/**
 * 
 */
package com.williamoneil.charts.server.controller.yahoo;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.MathContext;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.DateTimeZone;
import org.joda.time.Days;
import org.joda.time.Months;
import org.joda.time.Weeks;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import com.williamoneil.charts.server.ApplicationException;
import com.williamoneil.charts.shared.PeriodicityType;
import com.williamoneil.charts.shared.PriceVolumeData;
import com.williamoneil.charts.shared.TradeDateType;

/**
 * @author Gudipati
 *
 */
public class YahooPriceVolumeController {

	private static final String URL_TEMPLATE = "http://ichart.finance.yahoo.com/table.csv?s=%s&a=%s&b=%s&c=%s&d=%s&e=%s&f=%s&g=%s&ignore=.csv";
	//private static final String DIVIDEND_URL_TEMPLATE = "http://ichart.finance.yahoo.com/table.csv?s=%s&a=%s&b=%s&c=%s&d=%s&e=%s&f=%s&g=v&ignore=.csv";
	
	public YahooPriceVolumeController(){
	}
	
	@SuppressWarnings("deprecation")
	public List<PriceVolumeData> getPriceVolume(String symbolStr, PeriodicityType period, Calendar startDt, Calendar endDt) throws Exception {
		if(symbolStr == null || period == null || startDt == null) {
			return null;
		}
		
		final String symbol = URLEncoder.encode(symbolStr.trim());
		
		if(endDt == null) {
			endDt = Calendar.getInstance();
		}
		
		//final DateTimeFormatter sdf = DateTimeFormat.forPattern("yyyy-MM-dd");
		final DateTimeFormatter sdfUtc = DateTimeFormat.forPattern("yyyy-MM-dd").withZoneUTC();
		//final DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		
		
		final Map<DateTime, BigDecimal> divMap = new HashMap<DateTime, BigDecimal>();

		if(!symbolStr.startsWith("^")) {
			// not an index
			
			final String divUrlStr = String.format(URL_TEMPLATE, symbol, 
				startDt.get(Calendar.MONTH), startDt.get(Calendar.DAY_OF_MONTH), startDt.get(Calendar.YEAR),
				endDt.get(Calendar.MONTH), endDt.get(Calendar.DAY_OF_MONTH), endDt.get(Calendar.YEAR),
				"v");
		
			final URL divUrl = new URL(divUrlStr);

			final BufferedReader divReader = new BufferedReader(new InputStreamReader(divUrl.openStream()));
			String readLine = divReader.readLine(); // first line is header.. we can ignore it
			// process until end-of file
			while(true) {
				readLine = divReader.readLine();
				if(readLine == null) {
					break;
				}
						
				final StringTokenizer tokenz = new StringTokenizer(readLine, ",", false);
				DateTime dt = null;
				if(tokenz.hasMoreElements()) {
					dt = sdfUtc.parseDateTime(tokenz.nextToken());
				} else {
					break;
				}
				
				BigDecimal div = null;
				if(tokenz.hasMoreElements()) {
					div = new BigDecimal(tokenz.nextToken());
				} else {
					break;
				}
				
				divMap.put(dt, div);
			}
		}
		
		final String urlStr = String.format(URL_TEMPLATE, symbolStr, 
				startDt.get(Calendar.MONTH), startDt.get(Calendar.DAY_OF_MONTH), startDt.get(Calendar.YEAR),
				endDt.get(Calendar.MONTH), endDt.get(Calendar.DAY_OF_MONTH), endDt.get(Calendar.YEAR),
				period.getYahooType());
		
		final URL url = new URL(urlStr);
		
		final List<PriceVolumeData> list = new ArrayList<PriceVolumeData>();
		
		final BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
		String readLine = reader.readLine(); // first line is header.. we can ignore it
		
		BigDecimal rollingSplitFactor = new BigDecimal(1);
		BigDecimal rollingDiv = new BigDecimal(0);
		
		// holds the last added pv-data for holiday check
		PriceVolumeData lastProcessedData = null;
		
		// process until end-of file
		while(true) {
			readLine = reader.readLine();
			if(readLine == null) {
				break;
			}
			
			final StringTokenizer tokenz = new StringTokenizer(readLine, ",", false);
			int index = 0;
			
			final PriceVolumeData aData = new PriceVolumeData();
			aData.setTradeDateType(TradeDateType.TRADE_DATE);
			
			while(tokenz.hasMoreElements()) {
				index++;
				String aToken = tokenz.nextToken();
				if(aToken == null) {
					break;
				}
				
				if(index == 1) {
					final DateTime dt = sdfUtc.parseDateTime(aToken);
					aData.setTradeDate(dt.toDate().getTime());
				} else if (index == 2) {
					aData.setOpen(new BigDecimal(aToken, MathContext.DECIMAL32));
				} else if (index == 3) {
					aData.setHigh(new BigDecimal(aToken, MathContext.DECIMAL32));
				} else if (index == 4) {
					aData.setLow(new BigDecimal(aToken, MathContext.DECIMAL32));
				} else if (index == 5) {
					aData.setClose(new BigDecimal(aToken, MathContext.DECIMAL32));
				} else if (index == 6) {
					aData.setVolume(Long.parseLong(aToken));
				} else if (index == 7) {
					aData.setAdjClose(new BigDecimal(aToken, MathContext.DECIMAL32));
				} else {
					break;
				}
			}

			final BigDecimal div = divMap.get(aData.getTradeDate());
			
			if(!aData.getAdjClose().equals((aData.getClose().add(rollingDiv)).multiply(rollingSplitFactor))) {				
				final BigDecimal splitFactor = (aData.getClose().subtract(rollingDiv)).divide(aData.getAdjClose(), MathContext.DECIMAL32);
				if(splitFactor.compareTo(BigDecimal.ONE) != 0) {
					aData.setSplitRatio(splitFactor);
					rollingSplitFactor = rollingSplitFactor.multiply(splitFactor);
				}
			}
			
			// do div after the split calculation as yahoo reports the div at the begining of the trade-date
			if(div != null) {
				aData.setDividend(div);
				rollingDiv = rollingDiv.add(div);
			} 
			
			final DateTime currentDt = new DateTime(aData.getTradeDate());

			// reset to begining of the week (or month)  if it is not
			if(period == PeriodicityType.Weekly) {
				final DateTime weekendDt = currentDt.withDayOfWeek(DateTimeConstants.FRIDAY);
				aData.setTradeDate(weekendDt.toDate().getTime());
			} else if (period == PeriodicityType.Monthly) {
				final int maxDayInMonth = currentDt.dayOfMonth().getMaximumValue();
				final DateTime lastDayInMonth = currentDt.withDayOfMonth(maxDayInMonth);
				aData.setTradeDate(lastDayInMonth.toDate().getTime());
			}
			
			// check for gaps (holidays or non-trading periods)
			if(lastProcessedData != null && (period == PeriodicityType.Daily || period == PeriodicityType.Weekly)) {
				DateTime lastDt = new DateTime(lastProcessedData.getTradeDate(), DateTimeZone.UTC);
				
				int periodsDiff = 0;
				switch(period) {
				case Daily: {
					periodsDiff = Days.daysBetween(currentDt, lastDt).getDays();
					break;
				}
				case Weekly: {
					periodsDiff = Weeks.weeksBetween(currentDt, lastDt).getWeeks();
					break;
				}
				case Monthly: {
					periodsDiff = Months.monthsBetween(currentDt, lastDt).getMonths();
					break;
				}
				}
			
				if(periodsDiff > 1) {
					for(int i=1;i<periodsDiff;i++) {						
						DateTime newDt = null;
						switch(period) {
						case Daily: {
							newDt = lastDt.minusDays(1);
							break;
						}
						case Weekly: {
							newDt = lastDt.minusWeeks(1);
							newDt = newDt.withDayOfWeek(DateTimeConstants.FRIDAY);
							break;
						}
						case Monthly: {
							newDt = lastDt.minusMonths(1);
							int maxDayInMonth = newDt.dayOfMonth().getMaximumValue();
							newDt = newDt.withDayOfMonth(maxDayInMonth);
							break;
						}
						}
						
						lastDt = newDt;
						if(period == PeriodicityType.Daily) {
							if(newDt.getDayOfWeek() == DateTimeConstants.SATURDAY || newDt.getDayOfWeek() == DateTimeConstants.SUNDAY) {
								continue;
							}
						}

						final PriceVolumeData aGapData = new PriceVolumeData();
						aData.setTradeDateType(TradeDateType.TRADE_HOLIDAY);
						aGapData.setTradeDate(newDt.toDate().getTime());
						
						list.add(aGapData);
					}
				}
			}
			
			lastProcessedData = aData;
			// add to list
			list.add(aData);
		}
		
		return list;
	}
}
